﻿using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;

namespace FileTransformer
{
    public delegate void Logger(string text);

    public delegate void NewItemEvent(string name, IDataSource item);

    public class DataUtils
    {
        public static byte[] EncryptBytes(byte[] inputData, byte[] key, byte[] initialVector)
        {
            if (key == null || key.Length <= 0)
                throw new ArgumentNullException("Key");

            if (initialVector == null || initialVector.Length <= 0)
                throw new ArgumentNullException("initialVector");

            byte[] encrypted;
            using (var rijAlg = new RijndaelManaged { Key = key, IV = initialVector, Mode = CipherMode.CBC })
            {
                // Create a decrytor to perform the stream transform.
                var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

                using (var msEncrypt = new MemoryStream())
                {
                    using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                        csEncrypt.Write(inputData, 0, inputData.Length);

                    encrypted = msEncrypt.ToArray();
                }
            }

            return encrypted;
        }

        public static byte[] DecryptBytes(byte[] encriptedData, byte[] key, byte[] initialVector)
        {
            if (key == null || key.Length == 0)
                throw new ArgumentNullException("Key");

            if (initialVector == null || initialVector.Length == 0)
                throw new ArgumentNullException("initialVector");

            byte[] result;

            using (var rijAlg = new RijndaelManaged { Key = key, IV = initialVector, Mode = CipherMode.CBC })
            {
                // Create a decrytor to perform the stream transform.
                var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);

                using (var msDecrypt = new MemoryStream())
                {
                    using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
                        csDecrypt.Write(encriptedData, 0, encriptedData.Length);

                    result = msDecrypt.ToArray();
                }
            }

            return result;
        }

        public static byte[] Compress(byte[] inBytes)
        {
            using (var tmp = new MemoryStream())
            {
                using (var compress = new DeflateStream(tmp, CompressionMode.Compress))
                    compress.Write(inBytes, 0, inBytes.Length);

                return tmp.ToArray();
            }
        }

        public static byte[] Decompress(byte[] inBytes)
        {
            using (var inTmp = new MemoryStream())
            {
                inTmp.Write(inBytes, 0, inBytes.Length);
                inTmp.Seek(0, SeekOrigin.Begin);

                using (var outTmp = new MemoryStream())
                using (var decompress = new DeflateStream(inTmp, CompressionMode.Decompress))
                {
                    decompress.CopyTo(outTmp);
                    return outTmp.ToArray();
                }
            }
        }

        public static void SaveBytesToFile(string path, byte[] bytes, int offset = 0, int length = -1)
        {
            if (length == -1)
                length = bytes.Length;

            if (offset >= bytes.Length)
                throw new ArgumentException("offset");

            if ((offset + length) >= bytes.Length)
                length = bytes.Length - offset;

            using (var fileStream = new FileStream(path, FileMode.Create))
                fileStream.Write(bytes, offset, length);
        }

        public static byte[] ReadFromFile(string path)
        {
            return File.ReadAllBytes(path);
        }

        public static bool CompareBytes(byte[] one, byte[] two)
        {
            if (one.Length != two.Length)
                return false;

            return !one.Where((t, i) => t != two[i]).Any();
        }

        public static byte[] CompressAndCript(byte[] bytes, Identity identity)
        {
            return EncryptBytes(Compress(bytes), identity.Key, identity.IV);
        }

        public static byte[] DecompressAndDecript(byte[] bytes, Identity identity)
        {
            return Decompress(DecryptBytes(bytes, identity.Key, identity.IV));
        }
    }
}
